package cn.kinoko.common.utils;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author kk
 */
public class SpelUtil {

    // 解析缓存
    private static final Cache<String, Expression> EXPRESSION_CACHE = Caffeine.newBuilder()
            // 软引用value
            .softValues()
            // 当超过容量时，将会尝试通过基于LRU算法移除缓存
            .maximumSize(1024)
            // 5分钟未被再次访问时，将会被移除
            .expireAfterAccess(Duration.ofMinutes(5))
            .build();

    // 参数名称解析器
    private static final DefaultParameterNameDiscoverer DISCOVERER = new DefaultParameterNameDiscoverer();
    // spel表达式解析器
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();

    /**
     * 获取加锁的key
     * @param joinPoint 切点
     * @param key       key
     * @param params    参数
     * @return key
     */
    public static String getRedisKey(ProceedingJoinPoint joinPoint, String key, String[] params) {
        // 获取参数
        Object[] parameterValues = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切面方法
        Method method = signature.getMethod();
        // 如果key为空，则使用方法名作为key
        if ("".equals(key)) {
            key = method.getName();
        }
        // 获取参数名称
        String[] parameterNames = DISCOVERER.getParameterNames(method);
        StringBuilder keyBuilder = new StringBuilder(key);
        for (String param : params) {
            // 解析spel表达式
            Optional<Object> result = parseSpel(param, parameterNames, parameterValues, Object.class);
            if (result.isPresent() && !"".equals(result.get().toString())) {
                keyBuilder.append(":").append(result.get());
            } else {
                keyBuilder.append(":").append("null");
            }
        }
        return keyBuilder.toString();
    }

    /**
     * 获取布尔参数
     * @param joinPoint      切面
     * @param unless         unless
     * @param defaultBoolean 默认值
     * @return 获取布尔参数
     */
    public static boolean getBoolean(ProceedingJoinPoint joinPoint, String unless, boolean defaultBoolean) {
        // 获取参数
        Object[] parameterValues = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切面方法
        Method method = signature.getMethod();
        // 获取参数名称
        String[] parameterNames = DISCOVERER.getParameterNames(method);
        // 解析spel表达式
        Optional<Boolean> value = parseSpel(unless, parameterNames, parameterValues, Boolean.class);
        return value.orElse(defaultBoolean);
    }

    /**
     * 解析spel表达式
     * @param spel            spel表达式
     * @param parameterNames  参数名称
     * @param parameterValues 参数值
     * @param clazz           类型
     * @param <T>             泛型
     * @return 解析结果
     */
    public static <T> Optional<T> parseSpel(String spel, String[] parameterNames, Object[] parameterValues, Class<T> clazz) {
        if (spel != null && !spel.isEmpty() && parameterNames != null && parameterNames.length != 0) {
            // 从缓存中拿解析的表达式，没有则缓存
            Expression expression = EXPRESSION_CACHE.get(spel, PARSER::parseExpression);
            try {
                StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext();
                // 为表达式设置参数变量
                for (int i = 0; i < parameterValues.length; ++i) {
                    standardEvaluationContext.setVariable(parameterNames[i], parameterValues[i]);
                }
                return Optional.ofNullable(expression.getValue(standardEvaluationContext, clazz));
            } catch (EvaluationException e) {
                throw new RuntimeException("expression parse error please check parameter type or spel", e);
            }
        }
        return Optional.empty();
    }

    /**
     * 将时间转换为Duration
     * @param time     时间
     * @param timeUnit 时间单位
     * @return java.time.Duration
     */
    public static Duration toDuration(int time, TimeUnit timeUnit) {
        return Duration.of(time, toChronoUnit(timeUnit));
    }


    private static ChronoUnit toChronoUnit(TimeUnit timeUnit) {
        return switch (timeUnit) {
            case NANOSECONDS -> ChronoUnit.NANOS;
            case MICROSECONDS -> ChronoUnit.MICROS;
            case MILLISECONDS -> ChronoUnit.MILLIS;
            case SECONDS -> ChronoUnit.SECONDS;
            case MINUTES -> ChronoUnit.MINUTES;
            case HOURS -> ChronoUnit.HOURS;
            case DAYS -> ChronoUnit.DAYS;
            default -> throw new IllegalArgumentException("Unsupported TimeUnit: " + timeUnit);
        };
    }

}
